-- ArenaM
-- Created by chengb Dec/14/2015
-- 角斗台模块
-- 角斗台是一个特殊的夹层迷宫

module("ArenaM", package.seeall);

local PURE_FILE = "ArenaM";

-- 配置表
local arenaSlimeTable = {};
local arenaSlimesByStage = {};
local arenaMixedTable = {};
local arenaAudienceTable = {};
local arenaAudiencesByGroup = {};
local arenaTalkTable = {};
local arenaTalkByStage = {};

-- 怪物的class
local MONSTER_GRID_CLASS = 2;

-- 最后一场冈布奥编号
local FINAL_PET_ID = 75;

-- 触发观众入场发言所需击杀的观众数量
local KILL_NUM_FOR_ENTER_TALK = 10;

-- 每个阶段发言观众的数量
local MAX_TALK_NUM = 5;
local MIN_TALK_NUM = 3;

-- 每个格子发言冷却时间
local GRID_TALK_CD_TIME = 2.5;

-- 每个阶段发言的时间间隔
local STAGE_TALK_INTERVAL = 3;

-- 发言调度器执行间隔
local TALK_CHECK_INTERVAL = 1;

-- 战斗过程发言的优先级
local COMBAT_PROCESS_TALK = 1;

-- 玩家胜利发言的优先级
local PLAYER_WIN_TALK = 2;

-- 观众受创发言的优先级
local AUDIENCE_DAMAGE_TALK = 3;

-- 观众死亡发言的优先级
local AUDIENCE_DIED_TALK = 4;

-- 玩家入场发言的优先级
local PLAYER_ENTRANCE_TALK = 5;

-- 当前发言的优先级
local curTalkLevel = 1;

-- 当次角斗场是否播放过胜利发言
local hasWinTalk = false;

-- 观众发言队列
local audienceTalkQueue = {};

-- 受创观众临时列表
local damagedAudienceTempList = {};

-- 死亡观众临时列表
local deadAudienceTempList = {};

-- 最近一次观众入场发言的时间
local lastEnterTalkTime = 0;

-- 最近一次观众战斗过程发言的时间
local lastCombatTalkTime = 0;

-- 最近一次观众胜利发言的时间
local lastWinTalkTime = 0;

-- 最近一次观众受创的回合数
local lastAudienceDamagedRound = 0;

-- 最近一次观众死亡的回合数
local lastAudienceDieRound = 0;

-- 进入时的情形（例如首次进入，上次比赛胜利、上次比赛逃离）
local enterType;

-- 观众格子最近一次发言时间
local gridsLastTalkTimeMap = {};

-- 内部函数声明
local appearDoor;
local checkAudienceTalk;
local doWinBonus;
local fetchAudiences;
local fetchCombatSlime;
local fetchAudienceTalkPos;
local getKaitouAudienceTalkPos;
local isInTalkCD;
local prepareAttack;
local prepareCombatTalk;
local prepareDamagedTalk;
local prepareDeadTalk;
local prepareEnterTalk;
local prepareWinTalk;
local physic;
local execTalkQueue;
local resetTalkRecord;
local statResult;
local triggerAudienceTalk;
local useSkill;
local whenHeroDie;
local whenMonsterDie;
local whenLeaveDungeon;
local whenNextFloor;
local whenMonsterDamaged;
local whenEnterUnreal;
local whenPickItem;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    arenaSlimeTable = {};
    arenaSlimesByStage = {};
    arenaMixedTable = {};
    arenaAudienceTable = {};
    arenaAudiencesByGroup = {};
    arenaTalkTable = {};
    arenaTalkByStage = {};

    -- 载入角斗台冈布奥配置
    local arena_slime_CSV = get_arena_slime_CSV();
    for i = 1, #arena_slime_CSV do
        local classId = arena_slime_CSV[i].class_id;
        arenaSlimeTable[classId] = arena_slime_CSV[i];

        -- 根据出场阶位存放
        local stage = arena_slime_CSV[i].stage;
        if arenaSlimesByStage[stage] == nil then
            arenaSlimesByStage[stage] = {};
        end
        if table.indexOf(arenaSlimesByStage[stage], classId) == -1 then
            table.insert(arenaSlimesByStage[stage], classId);
        end
    end

    -- 根据classId升序
    for stage, _ in pairs(arenaSlimesByStage) do
        table.sort(arenaSlimesByStage[stage]);
    end

    -- 载入角斗台杂项配置
    local arena_mixed_CSV = get_arena_mixed_CSV();
    for i = 1, #arena_mixed_CSV do
        local key   = arena_mixed_CSV[i].key;
        local value = arena_mixed_CSV[i].value;
        arenaMixedTable[key] = value;
    end

    -- 载入观众配置表
    local arena_audience_CSV = get_arena_audience_CSV();
    for i = 1, #arena_audience_CSV do
        local classId = arena_audience_CSV[i].class_id;
        arenaAudienceTable[classId] = arena_audience_CSV[i];

        -- 根据分组存放
        local group = arena_audience_CSV[i].group;
        if arenaAudiencesByGroup[group] == nil then
            arenaAudiencesByGroup[group] = {};
        end
        if table.indexOf(arenaAudiencesByGroup[group], classId) == -1 then
            table.insert(arenaAudiencesByGroup[group], classId);
        end
    end

    -- 根据classId升序
    for group, _ in pairs(arenaAudiencesByGroup) do
        table.sort(arenaAudiencesByGroup[group]);
    end

    -- 载入观众发言配置表
    local arena_audience_talk_CSV = get_arena_audience_talk_CSV();
    for i = 1, #arena_audience_talk_CSV do
        local id = arena_audience_talk_CSV[i].id;
        arenaTalkTable[id] = arena_audience_talk_CSV[i];

        -- 根据观众分组（普通、特殊）和发言时机存放
        local group = arena_audience_talk_CSV[i].group;
        local stage = arena_audience_talk_CSV[i].stage;
        if arenaTalkByStage[group] == nil then
            arenaTalkByStage[group] = {};
        end
        if arenaTalkByStage[group][stage] == nil then
            arenaTalkByStage[group][stage] = {};
        end
        if table.indexOf(arenaTalkByStage[group][stage], id) == -1 then
            table.insert(arenaTalkByStage[group][stage], id);
        end
    end
end

-- 初始化
function init()
    loadCsv();

    -- 反注册所有事件
    EventMgr.removeAll(PURE_FILE);

    -- 关注怪物死亡的事件
    EventMgr.register(PURE_FILE, event.MONSTER_DIE_LOGIC, whenMonsterDie);

    -- 关注玩家离开迷宫的事件
    EventMgr.register(PURE_FILE, event.LEAVE_DUNGEON, whenLeaveDungeon);

    -- 关注回合开始的事件
    CombatM.registerStartRoundCB("arena_start_round", prepareAttack);

    -- 关注进入下一层的事件
    EventMgr.register(PURE_FILE, event.START_NEXT_FLOOR, whenNextFloor);

    -- 关注进入夹层的事件
    EventMgr.register(PURE_FILE, event.ENTER_UNREAL_DUNGEON, whenEnterUnreal);

    -- 关注怪物受创事件
    EventMgr.register(PURE_FILE, event.MONSTER_DAMAGED, whenMonsterDamaged);

    -- 关注玩家死亡的事件
    EventMgr.register(PURE_FILE, event.HERO_DIE, whenHeroDie);

    -- 关注拾取奖励的事件
    EventMgr.register(PURE_FILE, event.PICK_UP_ITEM, whenPickItem);
end

-- 检索角斗台冈布奥配置
function querySlime(classId, path)
    if not arenaSlimeTable[classId] then
        return nil;
    end

    if path == nil or path == "" then
        return arenaSlimeTable[classId];
    end

    return arenaSlimeTable[classId][path];
end

-- 检索杂项配置
function queryMixed(key)
    return arenaMixedTable[key];
end

-- 检索角斗台观众配置
function queryAudience(classId, path)
    if not arenaAudienceTable[classId] then
        return nil;
    end

    if path == nil or path == "" then
        return arenaAudienceTable[classId];
    end

    return arenaAudienceTable[classId][path];
end

-- 检索观众翻页配置
function queryAudienceTalk(id, path)
    if not arenaTalkTable[id] then
        return nil;
    end

    if path == nil or path == "" then
        return arenaTalkTable[id];
    end

    return arenaTalkTable[id][path];
end

-- 是否在角斗台中
function isInArena()
    if not UnrealDungeonM.isInUnreal() then
        return false;
    end

    local unrealId = UnrealDungeonM.getUnrealId();
    local dbase = UnrealDungeonM.query(unrealId, "dbase");
    return dbase["is_arena"] == 1;
end

-- 是否是冈布奥辅怪
function isChildSlime(slime)
    local dbase = ArenaM.querySlime(slime.classId, "dbase");
    if type(dbase) == "table" and dbase["child_slime"] == 1 then
        return true;
    end

    return false;
end

-- 能否进入角斗台
function canEnterArena()
    local slimeId = getCombatSlimeId();
    if type(slimeId) ~= "number" then
        -- 还没有抽取对手冈布奥
        return false;
    end

    return FormulaM.invoke("CAN_ENTER_ARENA", ME.user, slimeId);
end

-- 获取当前所处的出场阶位
function getStage()
    local layer = DungeonM.currentLayer();
    return FormulaM.invoke("CALC_ARENA_STAGE", layer);
end

-- 获取已胜利次数
function getWinTimes()
    local resultMap = ME.user.dbase:query("arena_record", "result", {});
    local winTimes = 0;
    for _, result in pairs(resultMap) do
        if result == ARENA_RESULT_WIN then
            winTimes = winTimes + 1;
        end
    end
    return winTimes;
end

-- 获取存活的敌方冈布奥列表
function getAliveEnemySlimes()
    if not isInArena() then
        -- 不在角斗台中
        return {};
    end

    local grids = DungeonM.getCurrentDungeon();
    local ret = {};
    for _, grid in pairs(grids) do
        repeat
            if not grid:isMonster() or grid.monster:isDead() then
                -- 过滤非怪物，或者已经死亡的怪物
                break;
            end

            if grid.monster:isSlime() then
                table.insert(ret, grid.monster);
            end
        until true
    end

    return ret;
end

-- 抽取角色格子
function fetchRoleGrids()
    -- 获取参战冈布奥所在格子位置
    local enemySlimePos  = queryMixed("enemy_slime_pos");
    assert(type(enemySlimePos)  == "number", "enemy_slime_pos字段必须为number类型");

    -- 获取参战冈布奥的元素编号
    local enemySlimeElem = queryMixed("enemy_slime_elem");
    assert(type(enemySlimeElem) == "number", "enemy_slime_elem字段必须为number类型");

    -- 获取观众所在的格子位置
    local audienceLayout = queryMixed("audinece_layout");
    assert(type(audienceLayout) == 'table', 'audinece_layout字段必须为table类型');

    -- 获取观众的元素编号
    local audienceElem = queryMixed("audience_elem");
    assert(type(audienceElem) == 'number', 'audience_elem字段必须为number类型');

    local ret = {};

    -- 抽取和玩家角斗的冈布奥
    ret[enemySlimePos] = { [MONSTER_GRID_CLASS] = enemySlimeElem, };

    -- 抽取观众
    for i = 1, #audienceLayout do
        local pos = audienceLayout[i];
        ret[pos] = { [MONSTER_GRID_CLASS] = audienceElem, };
    end

    return ret;
end

-- 抽取角色数据
function fetchRoleData()
    local ret = {};

    -- 抽取和玩家角斗的冈布奥
    ret.combatSlime = fetchCombatSlime();

    -- 抽取观众
    ret.audiences = fetchAudiences();

    -- 记录一个临时字段，等创建完观众格子后再删除
    ME.user.dbase:set("arena_record", "audiences_temp", table.deepcopy(ret.audiences));

    return ret;
end

-- 抽取观众
function fetchAudiences()
    local arenaRecord = ME.user.dbase:query("arena_record", {});
    local audiences = arenaRecord["audiences"];
    if type(audiences) == "table" then
        -- 已经抽过了，直接返回
        return audiences;
    end

    -- 特殊观众数量权重表
    local specialWeight = queryMixed("special_audience_num_weight");
    assert(type(specialWeight) == "table", "special_audience_num_weight字段必须为table类型");

    -- 1.抽取特殊观众
    local rand = DungeonM.getRandSeed("fetch_special_audience_num");
    local item = fetchElemBySeed(specialWeight, rand);
    local specialNum = item["num"];
    local ret = {};

    local waitListSpecial = arenaAudiencesByGroup[AUDIENCE_GROUP_SPECIAL];
    assert(type(waitListSpecial) == "table", "没有配置特殊观众");
    assert(#waitListSpecial >= specialNum, "特殊观众数量不足");

    local waitList = table.deepcopy(waitListSpecial);

    local pet = ME.user:getActivePet();
    local userPetId = pet.classId;
    local lictorPets = LictorM.getLictorPets();
    local enemySlimeId = getCombatSlimeId();
    local enemyPetId = querySlime(enemySlimeId, "pet_id");
    local forbidAudiences = arenaRecord["forbid_audiences"] or {};

    for _, id in pairs(waitListSpecial) do
        -- 选取的观众不能是场上的冈布奥（玩家的冈布奥、灵魂链接、与玩家角斗的冈布奥）
        -- 如果在forbid_audiences中，也不允许出现
        local dbase = queryAudience(id, "dbase");
        if dbase["pet_id"] == userPetId or
           dbase["pet_id"] == enemyPetId or
           table.indexOf(forbidAudiences, id) ~= -1 or
           table.indexOf(lictorPets, dbase["pet_id"]) ~= -1 then
            table.removeItem(waitList, id);
        end
    end

    if #waitList < specialNum then
        assert(false, string.format("可选的特殊观众数量不足，需求：%d，当前：%d", specialNum, #waitList));
    end

    -- 尝试抽取怪盗观众
    local kaitouNum = 0;
    repeat
        -- 已经抽取过怪盗了
        local kaitouId = queryMixed("kaitou_id");
        local kaitouIndex = table.indexOf(waitList, kaitouId);
        if kaitouIndex < 0 then
            break;
        end

        -- 不管是否抽中，本层的后续抽取都不能有怪盗参与
        table.remove(waitList, kaitouIndex);

        -- 不在目标楼层
        local kaitouLayers = queryMixed("kaitou_layer");
        local layer = DungeonM.currentLayer();
        if table.indexOf(kaitouLayers, layer) < 0 then
            break;
        end

        -- 概率不符合
        local ratio = queryMixed("kaitou_ratio");
        if DungeonM.getRandSeed("fetch_special_audience") % 1000 >= ratio then
            break;
        end

        -- 抽中了
        table.insert(ret, kaitouId);
        kaitouNum = 1;
    until true

    -- 有怪盗时，替换一个特殊观众；若无特殊观众，替代普通观众
    if specialNum > 0 then
        for i = 1, specialNum - kaitouNum do
            local rand = DungeonM.getRandSeed("fetch_special_audience");
            local index = rand % #waitList + 1; -- 注：客户端下标比服务大1
            local audienceId = waitList[index];
            table.insert(ret, audienceId);

            -- 特殊观众不能重复，因此抽过的需要移除
            table.remove(waitList, index);
        end
    else
        specialNum = specialNum + kaitouNum;
    end

    -- 2. 抽取普通观众
    local audienceLayout = queryMixed("audinece_layout");
    assert(type(audienceLayout) == 'table', 'audinece_layout字段必须为table类型');
    local normalNum = #audienceLayout - specialNum;

    local waitListNormal = arenaAudiencesByGroup[AUDIENCE_GROUP_NORMAL];
    assert(type(waitListNormal) == "table", "没有配置普通观众");
    assert(#waitListNormal > 0, "普通观众数量不足");

    waitList = table.deepcopy(waitListNormal);

    if normalNum > 0 then
        local fetchedStat = {};
        for i = 1, normalNum do
            local rand = DungeonM.getRandSeed("fetch_normal_audience");
            local index = rand % #waitList + 1; -- 注：客户端下标比服务大1
            local audienceId = waitList[index];
            table.insert(ret, audienceId);

            -- 统计已出现观众数量
            if fetchedStat[audienceId] then
                fetchedStat[audienceId] = fetchedStat[audienceId] + 1;
            else
                fetchedStat[audienceId] = 1;
            end

            -- 有数量限制的，抽满后需要移除
            local dbase = queryAudience(audienceId, "dbase") or {};
            if type(dbase["limit"]) == "number" and
               fetchedStat[audienceId] >= dbase["limit"] then
                table.remove(waitList, index);
            end
        end
    end

    -- 打乱位置
    local finalList = {};
    for i = 1, #audienceLayout do
        local rand = DungeonM.getRandSeed("shuffle_audience");
        local index = rand % #ret + 1;
        local audienceId = ret[index];
        table.insert(finalList, audienceId);
        table.remove(ret, index);
    end

    trace(PURE_FILE, "成功抽取特殊观众：%d个，普通观众：%d", specialNum, normalNum);

    arenaRecord["audiences"] = finalList;
    ME.user.dbase:set("arena_record", arenaRecord);

    return finalList;
end

-- 抽取和玩家角斗的冈布奥
function fetchCombatSlime()
    local arenaRecord = ME.user.dbase:query("arena_record", {});
    local slimeId = arenaRecord["combat_slime"];
    if type(slimeId) == "number" and slimeId > 0 then
        -- 已经抽过了，直接返回
        return slimeId;
    end

    -- 获取当前所处的出场阶位
    local stage = getStage();
    assert(type(arenaSlimesByStage[stage]) == 'table', string.format("出场阶位%d没有可选的冈布奥", stage));

    local waitList = table.deepcopy(arenaSlimesByStage[stage]);
    local userPet = ME.user:getActivePet();
    local userPetId = userPet.classId;  -- 玩家的出战冈布奥
    local appearedSlimes = ME.user.dbase:query("arena_record", "appeared_slimes", {}); -- 已出现过的冈布奥
    local lictorPets = LictorM.getLictorPets();  -- 玩家的灵魂链接

    for _, id in pairs(arenaSlimesByStage[stage]) do
        -- 除了斯巴达冈布奥
        -- 1、选取的冈布奥不能和玩家出战的冈布奥相同
        -- 2、已经出现过的不会再出现
        -- 3、选取的冈布奥不能在玩家的灵魂链接当中
        local petId = querySlime(id, "pet_id");
        if petId == FINAL_PET_ID then
            -- 啥也不做
        else
            if (petId == userPetId) or
                table.indexOf(appearedSlimes, id) ~= -1 or
                table.indexOf(lictorPets, petId) ~= -1 then
                table.removeItem(waitList, id);
            end
        end
    end

    if #waitList == 0 then
        -- 没有可选的冈布奥
        trace(PURE_FILE, string.format("出场阶位%d没有可选的冈布奥", stage));
        return 0;
    end

    local rand = DungeonM.getRandSeed("fetch_combat_slime");
    local index = rand % #waitList + 1; -- 注：客户端下标比服务大1
    local slimeId = waitList[index];

    DungeonLogM.addLog(string.format("[ArenaM]成功抽取参战冈布奥：%d", slimeId));

    -- 记录参战冈布奥
    arenaRecord["combat_slime"] = slimeId;
    ME.user.dbase:set("arena_record", arenaRecord);

    return slimeId;
end

-- 获取与玩家角斗的冈布奥怪对象
function getCombatSlime()
    local enemySlimePos = queryMixed("enemy_slime_pos");
    local grid = DungeonM.getGridByPos(enemySlimePos);
    return grid.monster;
end

-- 获取和玩家角斗的冈布奥编号
function getCombatSlimeId()
    local arenaRecord = ME.user.dbase:query("arena_record", {});
    return arenaRecord["combat_slime"];
end

-- 获取和玩家角斗过的冈布奥
function getSlimeInHistory()
    return ME.user.dbase:query("slime_in_history", {});
end

-- 设置和玩家角斗过的冈布奥
function setSlimeInHistory(slimes)
    ME.user.dbase:set("slime_in_history", slimes);
end

-- 获取角斗场冈布奥掉落过的物品
function getSlimeDropItems()
    return ME.user.dbase:query("slime_drop_items", {});
end

--记录角斗场冈布奥掉落的物品
function setSlimeDropItems(dropItems)
    ME.user.dbase:set("slime_drop_items", dropItems);
end

-- 获取观众
function getAudiences()
    local arenaRecord = ME.user.dbase:query("arena_record", {});
    return arenaRecord["audiences"] or {};
end

-- 取走观众编号（该函数仅限创建观众格子时使用）
function takeAudienceId()
    -- 观众编号是预先抽好放在一个列表中的，在创建格子时，每次从列表中取一个编号，并移除被取走的编号
    local audiences = ME.user.dbase:query("arena_record", "audiences_temp", {});
    if #audiences == 0 then
        trace(PURE_FILE, "### 可选观众列表为空！");
        return 0;
    end

    local id = audiences[1];
    table.remove(audiences, 1);

    if #audiences == 0 then
        ME.user.dbase:delete("arena_record", "audiences_temp");
    end

    DungeonLogM.addLog(string.format("[ArenaM]取走观众编号：%d", id));

    return id;
end

-- 离开角斗台
function leaveArena()
    if not UnrealDungeonM.leaveDungeon() then
        return false;
    end

    -- 注销调度器
    ScheduleM.deleteScheme("ArenaM");
    return true;
end

-- 逃离
function flee()
    -- 统计比赛结果（输了）
    statResult(false);

    -- 离开
    leaveArena();
    return true;
end

-- 能否领取角斗奖励
function canTakeBonus()
    return hasWon();
end

-- 本次角斗是否胜利了
function hasWon()
    if not isInArena() then
        return false;
    end

    local layer = DungeonM.currentLayer();
    local arenaResult = ME.user.dbase:query("arena_record", "result", {});
    return arenaResult[layer] == ARENA_RESULT_WIN;
end

-----------------------------------
-- 是否逃离过
-- @param layer 迷宫层数（如果不传，则默认判断是否在任意层逃离过）
function hasEverFlee(layer)
    local arenaResult = ME.user.dbase:query("arena_record", "result", {});
    if #table.keys(arenaResult) == 0 then
        -- 从未参加过角斗
        return false;
    end

    if type(layer) == "number" then
        return arenaResult[layer] == ARENA_RESULT_LOSE;
    end

    local list = table.values(arenaResult);
    return table.indexOf(list, ARENA_RESULT_LOSE) ~= -1;
end

-- 获取自动攻击轮次倒计时显示
function getAttackCountDown(slime)
    if #PropM.fetchProps(slime, "transfiguration") > 0 then
        -- 被变形了
        return nil;
    end

    local remainCD = getRemainSkillCD(slime);
    if type(remainCD) == "number" and remainCD <= 0 then
        return 1;
    end

    return remainCD;
end

-- 获取主动技能倒计时
function getRemainSkillCD(slime)
    local skillId = querySlime(slime.classId, "active_skill");
    if type(skillId) ~= "number" then
        -- 没有主动技能
        return nil;
    end

    -- 上次施放技能回合
    local skillRound = slime.skill_round or 1;

    local prop = PropM.combine(slime, "add_cd", "slime");

    local round = CombatM.getRound();
    local cd = querySlime(slime.classId, "cast_cycle");

    return math.max(cd + prop[3] - (round - skillRound), 0);
end

-- 是否是主动技能
function isActiveSkill(slime, skillId)
    local activeSkillId = querySlime(slime.classId, "active_skill");
    if type(activeSkillId) == "number" then
        return activeSkillId == skillId;
    end
    return false;
end

-- 计算怪物掉落
function calcDrop(monster)
    local classId = monster.classId;
    if monster:isSlime() then
        -- 冈布奥怪
        local rate = querySlime(classId, "drop_rate");
        local rand = DungeonM.getRandSeed("calc_arena_slime_drop") % 100;
        if rand > rate then
            -- 没有触发概率掉落
            trace(PURE_FILE, "冈布奥怪(%s)没有触发掉落。", monster:getName());
            return nil;
        end

        local bonus = querySlime(classId, "drop_bonus");
        if type(bonus) == "table" and #bonus > 0 then
            return bonus;
        elseif type(bonus) == "string" then
            -- 配置了公式计算
            return FormulaM.invoke(bonus, ME.user, classId);
        end
    elseif monster:isAudience() then
        -- 观众怪
        local bonus = queryAudience(classId, "drop_bonus");
        if type(bonus) == "table" and #bonus > 0 then
            -- 配置了固定掉落
            return bonus;
        elseif type(bonus) == "string" then
            -- 配置了公式计算
            local args = queryAudience(classId, "dbase");
            return FormulaM.invoke(bonus, ME.user, args);
        end
    end

    return nil;
end

-- 统计本次角斗已出的钻石掉落
function statDropedGem(amount)
    local dropedGem = ME.user.dbase:query("arena_record", "droped_gem", 0);
    dropedGem = dropedGem + amount;
    ME.user.dbase:set("arena_record", "droped_gem", dropedGem);
end

-- 获取本次角斗已出的钻石掉落
function getDropedGem()
    return ME.user.dbase:query("arena_record", "droped_gem", 0);
end

-- 统计玩家在本次迷宫中已通过角斗台获得的钻石数量
function statGainedGem(amount)
    local gainGem = getGainedGem();
    gainGem = gainGem + amount;
    ME.user.dbase:set("arena_record", "gain_gem", gainGem);
end

-- 获取玩家在本次迷宫中已通过角斗台获得的钻石数量
function getGainedGem()
    return ME.user.dbase:query("arena_record", "gain_gem", 0);
end

-- 统计玩家这一生已经通过角斗台获得的钻石数量
function statGainedGemLifetime(amount)
    local gainGem = getGainedGemLifetime();
    gainGem = gainGem + amount;
    ME.user.dbase:set("arena_gain_gem", gainGem);
end

-- 获取玩家这一生已经通过角斗台获得的钻石数量
function getGainedGemLifetime()
    return ME.user.dbase:query("arena_gain_gem", 0);
end

-- 能否反击
function canStrikeBack(slime)
    local config = querySlime(slime.classId);
    if not config then
        -- 不是冈布奥怪
        return false;
    end

    return config["dbase"]["strike_back"] == 1;
end

-------------------------
-- 统计比赛结果
-- @param isWin 是否胜利
function statResult(isWin)
    local result = iif(isWin, ARENA_RESULT_WIN, ARENA_RESULT_LOSE);
    local arenaRecord = ME.user.dbase:query("arena_record", {});
    local arenaResult = arenaRecord["result"] or {};

    local layer = DungeonM.currentLayer();

    -- 记录最近一次角斗结果
    arenaResult[layer] = result;
    arenaRecord["result"] = arenaResult;
    arenaRecord["is_last_win"] = iif(isWin, 1, 0);
    ME.user.dbase:set("arena_record", arenaRecord);

    if isWin then
        -- 抛出胜利事件
        EventMgr.fire(event.ARENA_WIN);

        -- 准备胜利发言
        prepareWinTalk();

        -- 执行胜利奖励
        doWinBonus();
    else
        -- 抛出失败事件
        EventMgr.fire(event.ARENA_LOSS);
    end
end

-- 出现传送门
function appearDoor()
    local doorPos = queryMixed("door_pos");
    local toClass = DungeonM.getClassByType(GRID_TYPE_DOOR);
    local para = { ["class"] = toClass, ["element"] = queryMixed("door_elem"), };

    DungeonM.transformGrid(toClass, doorPos, GRID_STATE_OPEN, para);
end

-- 使用主动技能（这里不进行条件判断）
function useSkill(slime, target, skillId)
    local round = CombatM.getRound();

    -- 记录施放技能的回合
    slime.skill_round = round + 1;

    DungeonLogM.addLog(string.format("[ArenaM](%s)使用技能%d攻击(%s)", slime:getName(), skillId, target:getName()));

    SkillM.apply(slime, target, skillId, round);

    return true;
end

-- 普通攻击
function physic(slime, target)
    local skillId = querySlime(slime.classId, "physic_skill");
    if type(skillId) ~= "number" then
        -- 没有配置普通攻击
        return false;
    end

    DungeonLogM.addLog(string.format("[ArenaM](%s)对(%s)进行普通攻击", slime:getName(), target:getName()));

    local round = CombatM.getRound();
    SkillM.apply(slime, target, skillId, round);

    return true;
end

-- 执行胜利奖励
function doWinBonus()
    -- 交给公式处理
    local slimeId = getCombatSlimeId();
    FormulaM.invoke("DO_ARENA_WIN_BONUS", ME.user, slimeId);
end

-- 是否有未领取的奖励
function hasBonusNotTaken()
    -- 如果对手冈布奥没死，没有奖励
    local slime = getCombatSlime();
    if slime and not slime:isDead() then
        return false;
    end

    local grids = DungeonM.getCurrentDungeon();

    for _, grid in pairs(grids) do
        if grid:isMonster() and grid.monster:isDead() and grid.bonus then
            -- 有奖励未拾取
            return true;
        end

        if grid.type == GRID_TYPE_ARENA_BOX and grid.state ~= GRID_STATE_DISABLE then
            -- 有储物箱未开启
            return true;
        end
    end

    return false;
end

-- 冈布奥怪自动攻击
prepareAttack = function()
    local slime = getCombatSlime();
    if not slime or slime:isDead() then
        -- 对手冈布奥已经死亡
        return;
    end

    -- 玩家已经死亡
    if ME.user:isDead() then
        return;
    end

    -- 获取存活的敌法冈布奥
    local slimes = getAliveEnemySlimes();
    if #slimes == 0 then
        return;
    end

    for _, slime in pairs(slimes) do
        local user = ME.user;

        -- 判断能否攻击玩家
        local ret = FormulaM.invoke("CAN_SLIME_MONSTER_ATTACK", user, slime);

        if true == ret then
            -- 优先使用主动技能
            local skillId = querySlime(slime.classId, "active_skill");
            if type(skillId) == "number" then
                local remainCD = getRemainSkillCD(slime);
                if remainCD <= 1 then
                    return useSkill(slime, user, skillId);
                end
            end

            -- 无法施放主动技能时，进行普通攻击
            physic(slime, user);
        else
            DungeonLogM.addLog(string.format("[ArenaM]%s无法攻击玩家，原因：%s", slime:getName(), tostring(ret)));
        end
    end

    return true;
end

-- 是否在发言冷却中
isInTalkCD = function(pos)
    local lastTalkTime = gridsLastTalkTimeMap[pos];
    if type(lastTalkTime) ~= "number" then
        return false;
    end

    if os.time() - lastTalkTime >= GRID_TALK_CD_TIME then
        return false;
    end

    return true;
end

-------------------------------
-- 触发观众发言
-- @param pos     格子位置
-- @param content 发言内容
-- @param delay   延时几秒后播放
triggerAudienceTalk = function(pos, content, delay)
    -- 动态获取玩家冈布奥名称
    if string.match(content, "{user_slime}") ~= nil then
        local userSlime = ME.user:getActivePet();
        content = string.gsub(content, "{user_slime}", PetM.query(userSlime.classId, "name"));
    end

    -- 动态获取对手冈布奥名称
    if string.match(content, "{enemy_slime}") ~= nil then
        local enemySlimeId = getCombatSlimeId();
        content = string.gsub(content, "{enemy_slime}", MonsterM.query(enemySlimeId, "name"));
    end

    -- 动态获取随机冈布奥名称
    if string.match(content, "{rand_slime}") ~= nil then
        local rand = math.random(1, 2);
        if rand == 1 then
            local enemySlimeId = getCombatSlimeId();
            content = string.gsub(content, "{rand_slime}", MonsterM.query(enemySlimeId, "name"));
        else
            local userSlime = ME.user:getActivePet();
            content = string.gsub(content, "{rand_slime}", PetM.query(userSlime.classId, "name"));
        end
    end

    delay = delay or 0;

    -- 记录该格子最近一次发言时间
    gridsLastTalkTimeMap[pos] = os.time() + delay;

    -- 抛出发言事件
    EventMgr.fire(event.AUDIENCE_TALK, { ["pos"] = pos, ["content"] = content, ["delay"] = delay,});
end

----------------------------------------------------
-- 抽取观众发言位置
-- @param maxNum         需要抽取的位置个数
-- @param audienceLayout 可供抽取的位置列表（如果没传，默认从所有观众位置中抽取）
-- @param requireGroup   需求的观众分组（普通、特殊，如果没传，默认不限）
fetchAudienceTalkPos = function(maxNum, audienceLayout, requireGroup)
    -- 抽取规则：
    -- 1、每次最多只有  maxNum 个观众发言
    -- 2、避免抽到相邻的位置
    -- 3、避免抽到已死亡的观众

    if type(maxNum) ~= 'number' then
        maxNum = math.random(MIN_TALK_NUM, MAX_TALK_NUM);
    end

    local rand  = math.random(1, 2);
    local isOdd = iif(rand % 2 == 0, true, false);

    local ret = {};

    -- 获取观众所在的格子位置
    audienceLayout = audienceLayout or queryMixed("audinece_layout");

    local function selectGrid()
        for _, pos in pairs(audienceLayout) do
            if #ret >= maxNum then
                break;
            end

            -- 挑选未死亡并且不在发言冷去中的观众
            local grid = DungeonM.getGridByPos(pos);
            if grid and grid.monster and not grid.monster:isDead() and not isInTalkCD(pos) then
                local group = queryAudience(grid.monster.classId, "group");
                if isOdd then
                    if pos % 2 == 0 then
                        if requireGroup == nil or requireGroup == group then
                            table.insert(ret, pos);
                        end
                    end
                else
                    if pos % 2 ~= 0 then
                        if requireGroup == nil or requireGroup == group then
                            table.insert(ret, pos);
                        end
                    end
                end
            end
        end
    end

    -- 筛选
    selectGrid();

    if #ret > 0 then
        return ret;
    end

    -- 如果没有抽到，则从相邻位置抽取
    isOdd = not isOdd;
    selectGrid();

    return ret;
end

-- 获得怪盗观众的发言位置
getKaitouAudienceTalkPos = function(posList)
    local audiences = getAudiences();
    if #audiences == 0 then
        -- 没有观众？
        return;
    end

    local kaitouId = queryMixed("kaitou_id");
    local index = table.indexOf(audiences, kaitouId);
    if index < 0 then
        return;
    end

    local keys = table.keys(audiences);
    local pos = keys[index];

    -- 没有相邻的发言观众
    if table.indexOf(posList, pos + 1) > 0 or table.indexOf(posList, pos - 1) > 0 then
        return;
    end

    -- 未死亡并且不在发言冷却中
    local grid = DungeonM.getGridByPos(pos);
    if grid and grid.monster and not grid.monster:isDead() and not isInTalkCD(pos) then
        return pos;
    end

    return;
end


-- 怪物死亡的回调
whenMonsterDie = function(args)
    if not isInArena() then
        -- 不在角斗台，不处理
        return;
    end

    local monster = args.monster;
    if not monster then
        return;
    end

    local classId = monster.classId;
    local baseClassId = MonsterM.query(classId, "base_class_id");
    if monster:isSlime() then
        -- 对手冈布奥死亡了
        if baseClassId == getCombatSlimeId() then
            -- 清除其他小怪
            DungeonM.clearEnemyMonster();

            -- 角斗胜利
            statResult(true);

            -- 出现传送门
            appearDoor();
        end

    elseif monster:isAudience() then
        -- 观众死亡
        -- 统计击杀数量
        local killNum = ME.user.dbase:query("arena_record", "kill_audience_num", 0);
        killNum = killNum + 1;
        ME.user.dbase:set("arena_record", "kill_audience_num", killNum);
        trace(PURE_FILE, "击杀观众数量更新为：%d", killNum);

        -- 如果是佣兵营地的冈布奥，或者是怪盗，标记为不再出现
        local dbase = queryAudience(baseClassId, "dbase");
        local petId = dbase["pet_id"];
        local kaitouId = queryMixed("kaitou_id");
        if PetShopM.isInPetShop(petId) or kaitouId == baseClassId then
            local forbidAudiences = ME.user.dbase:query("arena_record", "forbid_audiences", {});
            if table.indexOf(forbidAudiences, baseClassId) == -1 then
                table.insert(forbidAudiences, baseClassId);
                ME.user.dbase:set("arena_record", "forbid_audiences", forbidAudiences);
                printTable(forbidAudiences, "forbid_audiences字段更新为");
            end
        end

        -- 记录最近一次观众死亡的回合数（注意：这里的回合数事件是在死亡之后才抛出的）
        lastAudienceDieRound = CombatM.getRound() + 1;

        -- 加入到死亡临时队列
        local pos = args.pos;
        if table.indexOf(deadAudienceTempList, pos) == -1 then
            table.insert(deadAudienceTempList, pos);
        end
    end
end

-- 关注拾取奖励的事件
whenPickItem = function(args)
    if not isInArena() then
        return;
    end

    -- 如果获得了钻石，需要统计一下
    local bonus = args.bonus;
    if bonus[1] == 2 and bonus[2] == "gem" then
        local amount = bonus[3];

        statGainedGem(amount);

        statGainedGemLifetime(amount);
    end
end

-- 玩家离开迷宫的回调
whenLeaveDungeon = function(args)
    if ME.user.dbase:query("arena_record") ~= nil then
        ME.user.dbase:delete("arena_record");
    end
end

-- 玩家进入下一层的回调
whenNextFloor = function(args)
    -- 如果有参战的冈布奥或观众数据，需要移除，以便下次能重新抽取
    local arenaRecord = ME.user.dbase:query("arena_record");
    if type(arenaRecord) == "table" then
        if arenaRecord["combat_slime"] ~= nil then
            arenaRecord["combat_slime"] = nil;
        end

        if arenaRecord["audiences"] ~= nil then
            arenaRecord["audiences"] = nil;
        end

        if arenaRecord["audiences_temp"] ~= nil then
            arenaRecord["audiences_temp"] = nil;
        end

        if arenaRecord["droped_gem"] ~= nil then
            arenaRecord["droped_gem"] = nil;
        end
    end
end

-- 重置发言记录
function resetTalkRecord()
    lastEnterTalkTime = 0;
    lastCombatTalkTime = 0;
    lastWinTalkTime = 0;
    lastAudienceDamagedRound = 0
    lastAudienceDieRound = 0;
    curTalkLevel = 1;
    hasWinTalk = false;
    deadAudienceTempList = {};
    damagedAudienceTempList = {};
    gridsLastTalkTimeMap = {};
end

-- 玩家进入夹层迷宫的回调
whenEnterUnreal = function(args)
    if not isInArena() then
        -- 进入的不是角斗台，不处理
        return;
    end

    -- 获取参战冈布奥
    local slime = getCombatSlime();
    if slime then
        -- 标记该冈布奥已出现过
        local appearedSlimes = ME.user.dbase:query("arena_record", "appeared_slimes", {});
        table.insert(appearedSlimes, slime.classId);
        ME.user.dbase:set("arena_record", "appeared_slimes", appearedSlimes);

        -- 尝试触发出场召唤怪物
        local props = PropM.fetchProps(slime, "appear_do_summon");
        for _, prop in ipairs(props) do
            PropM.trigger(slime, prop[1], prop[2]);
        end
    end

    local audiences = getAudiences();
    if #audiences == 0 then
        -- 没有观众？
        return;
    end

    -- 重置发言记录
    resetTalkRecord();

    local arenaRecord = ME.user.dbase:query("arena_record", {});
    local arenaResult = arenaRecord["result"] or {};
    local killAudienceNum = arenaRecord["kill_audience_num"] or 0;

    if #table.keys(arenaResult) == 0 then
        -- 首次进入
        enterType = "first_enter";
    elseif killAudienceNum >= KILL_NUM_FOR_ENTER_TALK then
        -- 击杀观众数量达到指定值
        enterType = "kill_bloody";
    elseif arenaRecord["is_last_win"] == 1 then
        -- 上次比赛胜利
        enterType = "last_win";
    else
        -- 上次比赛逃离
        enterType = "last_flee";
    end

    -- 准备入场发言
    prepareEnterTalk(enterType);

    -- 启动观众发言调度器
    ScheduleM.createScheme("ArenaM", checkAudienceTalk, TALK_CHECK_INTERVAL, true)

    -- 抛出入场事件
    EventMgr.fire(event.ENTER_ARENA, { ["enterType"] = enterType, });
end

-- 怪物受创的回调
whenMonsterDamaged = function(args)
    if not isInArena() then
        -- 不在角斗台，不处理
        return false;
    end

    local pos     = args.pos;
    local skillId = args.skillId;

    if type(skillId) ~= "number" or skillId <= 0 then
        -- 不是被技能所伤
        return;
    end

    local grid = DungeonM.getGridByPos(pos);
    if not grid then
        return;
    end

    local monster = grid.monster;
    if monster:isAudience() then
        -- 加入到观众受创临时队列
        local classId = monster.classId;
        if table.indexOf(damagedAudienceTempList, pos) == -1 then
            table.insert(damagedAudienceTempList, pos);
        end

        if hasWon() then
            hasWinTalk = true;
        end

        -- 记录最近一次观众受创的回合数
        lastAudienceDamagedRound = CombatM.getRound();
    end
end

-- 玩家死亡的回调
whenHeroDie = function()
    if not isInArena() then
        -- 不在角斗台中，不处理
        return;
    end

    EventMgr.fire(PURE_FILE, event.ARENA_USER_DIE);
end

-- 定时检测观众发言请求
checkAudienceTalk = function()
    if not isInArena() then
        -- 已经不在角斗台了
        return;
    end

    -- 如果发言队列不为空，直接响应请求
    if #audienceTalkQueue > 0 then
        execTalkQueue();
        return;
    end

    -- 发言优先级：入场 > 死亡 > 受创 > 胜利 > 战斗过程
    if CombatM.getRound() <= 1 then
        -- 玩家还没起手，准备入场发言
        prepareEnterTalk(enterType);
    elseif #deadAudienceTempList > 0 then
        -- 死亡队不为空，准备死亡发言
        prepareDeadTalk();
    elseif not hasWinTalk and hasWon() then
        -- 有观众受创的前提下，下一轮玩家胜利了，那么开始播放胜利发言，直到下次状态变化回到之前的发言队列
        curTalkLevel = PLAYER_WIN_TALK;
        prepareWinTalk();
    elseif #damagedAudienceTempList > 0 then
        -- 受创队列不为空，准备受创发言
        prepareDamagedTalk();
    elseif hasWon() then
        -- 玩家已经胜利了，准备胜利发言
        prepareWinTalk();
    else
        -- 准备战斗过程中的发言
        prepareCombatTalk();
    end
end

-- 响应发言队列中的请求
execTalkQueue = function()
    if #audienceTalkQueue == 0 then
        return;
    end

    -- 如果队列中有发言请求，逐个触发
    for _, item in pairs(audienceTalkQueue) do
        local pos   = item.pos;
        local delay = item.delay;
        local content = queryAudienceTalk(item.id, "content");
        triggerAudienceTalk(pos, content, delay);
    end

    audienceTalkQueue = {};
end

-- 准备入场发言（仅限普通观众和怪盗）
prepareEnterTalk = function(enterType)
    local curTime = os.time();
    if curTime - lastEnterTalkTime < STAGE_TALK_INTERVAL then
        -- 入场发言冷却中
        return;
    end

    local group = AUDIENCE_GROUP_NORMAL;
    local stage = AUDIENCE_TALK_STAGE_ENTER;
    local waitList = arenaTalkByStage[group][stage];
    if type(waitList) ~= 'table' or #waitList == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", group, stage);
        return;
    end

    -- 根据进入时的情形重新筛选一遍
    local finalList = {};
    for _, id in pairs(waitList) do
        local dbase = queryAudienceTalk(id, "dbase");
        if type(dbase) == "table" and dbase[enterType] == 1 then
            table.insert(finalList, id);
        end
    end

    -- 随机挑选几个发言位置
    local posList = fetchAudienceTalkPos(nil, nil, AUDIENCE_GROUP_NORMAL);
    if #finalList < #posList then
        trace(PURE_FILE, "入场发言失败，可选的对白数量不足！需求：%d，当前:%d", #posList, #finalList);
        return;
    end

    -- 记录最近一次入场发言的时间
    lastEnterTalkTime = os.time();

    -- 先清空发言队列
    audienceTalkQueue = {};

    for _, pos in pairs(posList) do
        local index = math.random(1, #finalList);
        local id = finalList[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;

        local item = { ["pos"] = pos, ["id"] = id, ["delay"] = delay};
        table.insert(audienceTalkQueue, item);

        -- 已选过的要从列表中移除
        table.removeItem(finalList, id);
    end

    -- 怪盗的发言
    local kaitouPos = getKaitouAudienceTalkPos(posList);
    if type(kaitouPos) == "number" then
        local talkList = arenaTalkByStage[AUDIENCE_GROUP_SPECIAL][stage];
        if type(talkList) ~= 'table' or #talkList == 0 then
            trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_SPECIAL, stage);
            return;
        end

        local index = math.random(1, #talkList);
        local id = talkList[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;
        local item = { ["pos"] = kaitouPos, ["id"] = id, ["delay"] = delay};
        table.insert(audienceTalkQueue, item);
    end
end

-- 准备战斗过程中发言
prepareCombatTalk = function()
    local curTime = os.time();

    if (curTime - lastCombatTalkTime) < STAGE_TALK_INTERVAL then
        -- 战斗过程发言冷却中
        return;
    end

    if curTalkLevel > COMBAT_PROCESS_TALK then
        return;
    end

    curTalkLevel = COMBAT_PROCESS_TALK;
    local stage = AUDIENCE_TALK_STAGE_COMBAT;

    -- 获取普通观众的发言模板
    local waitListNormal = arenaTalkByStage[AUDIENCE_GROUP_NORMAL][stage];
    if type(waitListNormal) ~= 'table' or #waitListNormal == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_NORMAL, stage);
        return;
    end

    -- 获取特殊观众的发言模板
    local waitListSpecial = arenaTalkByStage[AUDIENCE_GROUP_SPECIAL][stage];
    if type(waitListSpecial) ~= 'table' or #waitListSpecial == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_SPECIAL, stage);
        return;
    end

    waitListNormal  = table.deepcopy(waitListNormal);
    waitListSpecial = table.deepcopy(waitListSpecial);

    -- 随机挑选几个发言位置
    local posList = fetchAudienceTalkPos();

    local audiences = getAudiences();

    for _, pos in pairs(posList) do
        local audienceId = audiences[pos];
        local group = queryAudience(audienceId, "group");
        local waitList = iif(group == AUDIENCE_GROUP_SPECIAL, waitListSpecial, waitListNormal);

        local index = math.random(1, #waitList);
        local id = waitList[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;

        local item = { ["pos"] = pos, ["id"] = id, ["delay"] = delay,};
        table.insert(audienceTalkQueue, item);

        -- 已选过的要从列表中移除
        table.removeItem(waitList, id);
    end

    -- 记录最近一次战斗发言的时间
    lastCombatTalkTime = curTime;
end

-- 准备观众受创发言
prepareDamagedTalk = function()
    if #damagedAudienceTempList == 0 then
        return;
    end

    if curTalkLevel > AUDIENCE_DAMAGE_TALK then
        return;
    end

    curTalkLevel = AUDIENCE_DAMAGE_TALK;
    local stage = AUDIENCE_TALK_STAGE_DAMAGED;

    -- 获取普通观众的发言模板
    local waitListNormal = arenaTalkByStage[AUDIENCE_GROUP_NORMAL][stage];
    if type(waitListNormal) ~= 'table' or #waitListNormal == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_NORMAL, stage);
        return;
    end

    -- 获取特殊观众的发言模板
    local waitListSpecial = arenaTalkByStage[AUDIENCE_GROUP_SPECIAL][stage];
    if type(waitListSpecial) ~= 'table' or #waitListSpecial == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_SPECIAL, stage);
        return;
    end

    waitListNormal  = table.deepcopy(waitListNormal);
    waitListSpecial = table.deepcopy(waitListSpecial);

    -- 随机从受创列表中挑选几个
    local posList = fetchAudienceTalkPos(MAX_TALK_NUM, damagedAudienceTempList);

    -- 先清空发言队列
    audienceTalkQueue = {};

    local audiences = getAudiences();
    for _, pos in pairs(posList) do
        local audienceId = audiences[pos];
        local group = queryAudience(audienceId, "group");
        local waitList = iif(group == AUDIENCE_GROUP_SPECIAL, waitListSpecial, waitListNormal);

        local index = math.random(1, #waitList);
        local id = waitList[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;

        local item = { ["pos"] = pos, ["id"] = id, ["delay"] = delay};
        table.insert(audienceTalkQueue, item);

        -- 已选过的要从列表中移除
        table.removeItem(waitList, id);
    end
end

-- 准备观众死亡发言（目前仅限普通观众）
prepareDeadTalk = function()
    if #deadAudienceTempList == 0 then
        return;
    end

    if curTalkLevel > AUDIENCE_DIED_TALK then
        return;
    end

    curTalkLevel = AUDIENCE_DIED_TALK;
    local stage = AUDIENCE_TALK_STAGE_DIE;

    -- 获取普通观众的发言模板
    local waitListNormal = arenaTalkByStage[AUDIENCE_GROUP_NORMAL][stage];
    if type(waitListNormal) ~= 'table' or #waitListNormal == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_NORMAL, stage);
        return;
    end

    waitListNormal = table.deepcopy(waitListNormal);

    -- 随机从未死亡的观众列表中挑选几个
    local posList = fetchAudienceTalkPos(MAX_TALK_NUM);

    -- 清空受创队列
    damagedAudienceTempList = {};

    -- 先清空发言队列
    audienceTalkQueue = {};

    local audiences = getAudiences();
    for _, pos in pairs(posList) do
        local audienceId = audiences[pos];
        local group = queryAudience(audienceId, "group");

        local index = math.random(1, #waitListNormal);
        local id = waitListNormal[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;

        local item = { ["pos"] = pos, ["id"] = id, ["delay"] = delay};
        table.insert(audienceTalkQueue, item);

        -- 已选过的要从列表中移除
        table.removeItem(waitListNormal, id);
    end
end

-- 准备胜利发言（仅限普通观众）
prepareWinTalk = function()
    local curTime = os.time();
    if curTime - lastWinTalkTime < STAGE_TALK_INTERVAL then
        -- 胜利发言冷却中
        return;
    end

    if curTalkLevel > PLAYER_WIN_TALK then
        return;
    end

    curTalkLevel = PLAYER_WIN_TALK;
    local stage = AUDIENCE_TALK_STAGE_WIN;

    -- 获取普通观众的发言模板
    local waitListNormal = arenaTalkByStage[AUDIENCE_GROUP_NORMAL][stage];
    if type(waitListNormal) ~= 'table' or #waitListNormal == 0 then
        trace(PURE_FILE, "不存在group = %d, stage = %d的观众发言", AUDIENCE_GROUP_NORMAL, stage);
        return;
    end

    waitListNormal = table.deepcopy(waitListNormal);

    -- 随机从未死亡的观众列表中挑选几个
    local posList = fetchAudienceTalkPos();

    -- 先清空发言队列
    audienceTalkQueue = {};

    -- 记录最近一次胜利发言的时间
    lastWinTalkTime = curTime;

    local audiences = getAudiences();
    for _, pos in pairs(posList) do
        local audienceId = audiences[pos];
        local group = queryAudience(audienceId, "group");

        local index = math.random(1, #waitListNormal);
        local id = waitListNormal[index];

        -- 随机延时一下，错开发言时间
        local delay = math.random(1, 10)/10;

        local item = { ["pos"] = pos, ["id"] = id, ["delay"] = delay, };
        table.insert(audienceTalkQueue, item);

        -- 已选过的要从列表中移除
        table.removeItem(waitListNormal, id);
    end
end



